package IC;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import java_cup.runtime.Symbol;
import IC.AST.ASTNode;
import IC.AST.ICClass;
import IC.AST.PrettyPrinter;
import IC.AST.Program;
import IC.LIR.LabelingVisitor;
import IC.LIR.LirUpType;
import IC.LIR.TranslationVisitor;
import IC.Parser.Lexer;
import IC.Parser.LibraryParser;
import IC.Parser.Parser;
import IC.Parser.SyntaxError;
import IC.SemanticChecks.ScopeCheckingVisitor;
import IC.SemanticChecks.TablesConstructorVisitor;
import IC.SemanticChecks.TypeCheckingVisitor;
import IC.Symbols.GlobalSymbolTable;
import IC.Types.TypeTable;

public class Compiler
{
	private enum Type{
		library,
		IC
	}

	public static void main(String[] args) {
		ArgumentHandler argsHandler = new ArgumentHandler(args);

		//checks if the program called with the correct usage
		if (argsHandler.hasError()){
			System.out.println(argsHandler.getUsageMessage());
			return;
		}

		//checks if the the ICI file path supplied is valid
		if (!argsHandler.checkICFile()){
			System.out.println("The IC File " + argsHandler.getICFilePath() + " doesn't exist");
			return;
		}
		//checks if the library.sig supplied, and if so - valid
		if (!argsHandler.checkLibraryFile()){
			System.out.println("The library file " + argsHandler.getLibraryFilePath() + " doesn't exist");
			return;
		}

		//parse the library file
		ICClass rootLibrary = null;
		rootLibrary = parseLibrary(argsHandler.getLibraryFilePath(), argsHandler.getLibraryName());
		if (rootLibrary == null){
				return;
		}

		//parse the IC program file supplied
		Program rootIC = parseIC(argsHandler.getICFilePath(), argsHandler.getICName());
		if (rootIC == null){
			return;
		}
		System.out.print("\n");
		
		//construct Symbol tables
		String nameOfProg = getFileName(new File(argsHandler.getICFilePath()));	
		TypeTable typeTable = TypeTable.getInstance(nameOfProg);		
		if (typeTable == null){
			return;
		}

		GlobalSymbolTable globalSymbolTable = constructSymbolTables(rootIC, rootLibrary, getFileName(new File(argsHandler.getICFilePath())));
		if (globalSymbolTable == null){
			return;
		}

		//run semantic checks
		if (!runSemanticChecks(rootIC)){
			return;
		}
		
		//create labeling for each astNode using the Sethi-Ullman algorithm
		rootIC.accept(new LabelingVisitor());
		
		//create LIR code for the IC file
		TranslationVisitor translationVisitor = new TranslationVisitor();
		LirUpType translation = rootIC.accept(translationVisitor, null);
		
		//create LIR file (filename.lir for filename.ic - if flagged 
		if (argsHandler.hasPrintLirFlag()){
			String lirFilePath = createPathForLirFile(argsHandler.getICFilePath());
			try {
				BufferedWriter bw = new BufferedWriter(new FileWriter(lirFilePath));
				bw.write(translation.getLirCode());
				bw.close();
				
			} catch (IOException e) {
				System.err.println(e.getMessage());
			}
			
		}
		

		//print the AST using prettyPrinter - if flagged
		if (argsHandler.hasPrintAstFlag()){
			if (argsHandler.hasDebugFlag()){
				BufferedWriter outFile = null;
				try {
					outFile = new BufferedWriter(new FileWriter(getPathOutputFile(argsHandler.getICFilePath())));
					outFile.write("Parsed " + argsHandler.getICName() + " successfully!" + "\r\n" + getASTString(rootIC, argsHandler.getICFilePath()));
				} catch (IOException e) {
					System.out.println("Error: " + e.getMessage());
				} finally{
					if (outFile != null){
						try{
							outFile.close();
						} catch (IOException e) {
							System.out.println("Error: " + e.getMessage());
						}
					}
				}
			} else {
				printAST(rootIC, argsHandler.getICFilePath());
			}
		}

		//print typeTable and symbol tables - if flagged
		if (argsHandler.hasDumpSymTabFlag()){  
			if (globalSymbolTable != null){ //checking anyway although it wont be null at this point
				if (!argsHandler.hasDebugFlag()){
					System.out.println(globalSymbolTable);
					System.out.println(typeTable);
				} else {
					BufferedWriter outFile = null;
					try {
						File file = new File (getPathOutputFile(argsHandler.getICFilePath()));
						file.createNewFile();
						outFile = new BufferedWriter(new FileWriter(file));
						outFile.write(globalSymbolTable.toString());

					} catch (IOException e) {
						System.out.println("Error: " + e.getMessage());
					} finally{
						if (outFile != null){
							try{
								outFile.close();
							} catch (IOException e) {
								System.out.println("Error: " + e.getMessage());
							}
						}
					}
				}
			}
		}
	}

	//prints the AST for the given root
	private static void printAST(ASTNode root, String path){
		System.out.println(getASTString(root, path));
	}

	//returns the string represtation of the AST given by the root
	private static String getASTString(ASTNode root, String path){
		PrettyPrinter printer = new PrettyPrinter(path);
		return (String) root.accept(printer);
	}

	private static GlobalSymbolTable constructSymbolTables(Program rootIC, ICClass libraryClass, String nameOfProg) {

		GlobalSymbolTable gst = new GlobalSymbolTable(nameOfProg);
		GlobalSymbolTable resultGst = (GlobalSymbolTable) rootIC.accept(new TablesConstructorVisitor(gst, libraryClass));

		return resultGst; //whether its null or not
	}

	private static boolean runSemanticChecks(Program ICroot){
		if (ICroot.accept(new ScopeCheckingVisitor()) == null){
			return false;
		}

		if (ICroot.accept(new TypeCheckingVisitor()) == null){
			return false;
		}

		return true;

	}


	//parse the library.sig file
	private static ICClass parseLibrary(String libraryFile, String filename){
		return (ICClass) parseFile(libraryFile, filename, Type.library);
	}

	//parse the IC program file
	private static Program parseIC(String icFile, String filename){
		return (Program) parseFile(icFile, filename, Type.IC);
	}

	//parse an IC file
	private static ASTNode parseFile(String path, String filename, Type type){
		FileReader txtFile = null;
		Lexer scanner = new Lexer(txtFile);
		Symbol parseResult = null;
		try{ 
			txtFile = new FileReader(path);
			scanner = new Lexer(txtFile);
			if (type == Type.library){
				LibraryParser libraryParser = new LibraryParser(scanner);
				parseResult = libraryParser.parse();

			} else{
				Parser icParser = new Parser(scanner);
				parseResult = icParser.parse();
			}
			System.out.println("Parsed " + filename + " successfully!");
			if (txtFile != null){
				txtFile.close();
			}

		} catch (SyntaxError e){
			System.err.println(e);
		} catch (IOException e){
			System.err.println("Error: " + e);
		} catch (Exception e){
			System.err.println("Error: " + e);
		}

		if (parseResult == null){
			return null;
		}
		else {
			return (ASTNode) parseResult.value;
		}
	}
	
	//return the path for the designated lir file (pathToIC\file.ic --> pathToIC\file.lir)
	private static String createPathForLirFile(String icFilePath){
		return icFilePath.replace(".ic", ".lir");
	}

	//return the path for the output file (in debug mode)
	private static String getPathOutputFile(String path) throws IOException{
		File file = new File(path);
		return file.getParent() + File.separatorChar + "our" + File.separatorChar + getFileName(file) + ".out.txt";	
	}

	//returns the name of the file supplied (without the extention)
	private static String getFileName(File file){
		return file.getName();
	}
}